#include <stdlib.h>

#include <zephyr.h>
#include <shell/shell.h>

#if !defined(CONFIG_SHELL)
#undef shell_print
#undef shell_hexdump
#define shell_print(_sh, _ft, ...)
#define shell_hexdump(...)
#endif

#include <drivers/flash.h>
#include <storage/flash_map.h>
#include <fs/nvs.h>

static struct nvs_fs fs;

#define STORAGE_NODE_LABEL storage


static int nvs_cmd_show(const struct shell *shell, size_t argc, char **argv)
{
	shell_print(shell, "offset 0x%x", fs.offset);
	shell_print(shell, "ate_wra %d", fs.ate_wra);
	shell_print(shell, "data_wra %d", fs.data_wra);
	shell_print(shell, "ready %d", fs.ready);
	shell_print(shell, "sector_size 0x%x", fs.sector_size);
	shell_print(shell, "sector_count %d", fs.sector_count);
	shell_print(shell, "flash_device %p name %s", fs.flash_device, fs.flash_device->name);
	shell_print(shell, "erase_value 0x%x", fs.flash_parameters->erase_value);
	shell_print(shell, "write_block_size %d", fs.flash_parameters->write_block_size);
	shell_print(shell, "free space %d", nvs_calc_free_space(&fs));
	return 0;
}

static int nvs_cmd_init(const struct shell *shell, size_t argc, char **argv)
{
	int rc = 0;
	struct flash_pages_info info;
	const struct device *flash_dev;

	flash_dev = FLASH_AREA_DEVICE(STORAGE_NODE_LABEL);
	if (!device_is_ready(flash_dev)) {
		printk("Flash device %s is not ready\n", flash_dev->name);
		return 0;
	}
	fs.offset = FLASH_AREA_OFFSET(storage);
	rc = flash_get_page_info_by_offs(flash_dev, fs.offset, &info);
	if (rc) {
		printk("Unable to get page info\n");
		return 0;
	}
	fs.sector_size = info.size * 2;
	fs.sector_count = 3U;

	rc = nvs_init(&fs, flash_dev->name);
	if (rc) {
		printk("Flash Init failed\n");
		return 0;
	}

	return 0;
}

static int nvs_cmd_erase(const struct shell *shell, size_t argc, char **argv)
{
	int rc = 0;

	rc = nvs_clear(&fs);
	shell_print(shell, "nvs_clear return %d", rc);
	return 0;
}

static int nvs_cmd_write(const struct shell *shell, size_t argc, char *argv[])
{
	if (argc < 4) {
		return -EINVAL;
	}

	uint16_t id = strtoul(argv[1], NULL, 0);
	uint8_t value = strtoul(argv[2], NULL, 0);
	uint16_t len = strtoul(argv[3], NULL, 0);

	if (len < 1 && len >= fs.sector_size) {
		shell_print(shell, "len %u no fit", len);
		return 0;
	}

	uint8_t *data = k_malloc(len);
	if (data == NULL) {
		shell_print(shell, "Alloc memory len %u fail", len);
		return 0;
	}

	memset(data, value, len);

	int rc = nvs_write(&fs, id, data, len);
	shell_print(shell, "nvs_write id %d len %d return %d", id, len, rc);

	k_free(data);

	return 0;
}


static int nvs_cmd_read(const struct shell *shell, size_t argc, char *argv[])
{
	if (argc < 3) {
		return -EINVAL;
	}

	uint16_t id = strtoul(argv[1], NULL, 0);
	uint16_t len = strtoul(argv[2], NULL, 0);

	if (len < 1 && len >= fs.sector_size) {
		shell_print(shell, "len %u no fit", len);
		return 0;
	}

	uint8_t *data = k_malloc(len);
	if (data == NULL) {
		shell_print(shell, "Alloc memory len %u fail", len);
		return 0;
	}

	int rc = nvs_read(&fs, id, data, len);
	shell_print(shell, "nvs_write id %d len %d return %d", id, len, rc);
	shell_hexdump(shell, data, rc);

	k_free(data);

	return 0;
}

static int nvs_cmd_delete(const struct shell *shell, size_t argc, char *argv[])
{
	if (argc < 2) {
		return -EINVAL;
	}

	uint16_t id = strtoul(argv[1], NULL, 0);


	int rc = nvs_delete(&fs, id);
	shell_print(shell, "nvs_delete id %d return %d", id, rc);

	return 0;
}

SHELL_STATIC_SUBCMD_SET_CREATE(sub_nvs,
			       SHELL_CMD(show, NULL, "show nvs info", nvs_cmd_show),
			       SHELL_CMD(init, NULL, "nvs init", nvs_cmd_init),
			       SHELL_CMD(erase, NULL, "nvs erase", nvs_cmd_erase),
			       SHELL_CMD_ARG(write, NULL, "nvs write", nvs_cmd_write, 4, 0),
			       SHELL_CMD_ARG(read, NULL, "nvs read", nvs_cmd_read, 3, 0),
			       SHELL_CMD_ARG(delete, NULL, "nvs delete", nvs_cmd_delete, 2, 0),
			       SHELL_SUBCMD_SET_END
			       );

SHELL_CMD_REGISTER(nvs, &sub_nvs, "nvs test commands", NULL);